#include "CSerialCommandControllor.h"
#include <sstream>
#include <iomanip>
#include <string>
#include <QDebug>

#include "asio.hpp"

namespace zl
{
namespace zlmodules
{

struct CSerialCommandControllor::IOReply
{
    unsigned char data[1024];
    size_t len;

    IOReply() : data{0x00}, len(0) {}
};

struct CSerialCommandControllor::IOImpl
{
    asio::io_context io;
    asio::serial_port serial;
    IOImpl() : serial(io)
    {
    }
};

unsigned char genChk(const unsigned char *vptr, uint32_t len)
{
    int size = len;

    int index;
    unsigned char chk;

    chk = vptr[0];
    index = 1;

    for (; index < size; ++index)
        chk ^= vptr[index];

    return chk;
}

CSerialCommandControllor::CSerialCommandControllor(ISerialCommandEventNotice* obs)
    : io_(new IOImpl()),
      io_reply_(new IOReply()),
      io_read_flag_(false),
      io_read_thread_(nullptr),
      obs_(obs)
{
}

CSerialCommandControllor::~CSerialCommandControllor()
{
    Disconnect();
    if (io_ != nullptr)
    {
        delete io_;
        io_ = nullptr;
    }
}

int32_t CSerialCommandControllor::Connect(const std::string &port, int32_t baud)
{
    if (io_ == nullptr)
    {
        return -1;
    }

    if (io_->serial.is_open())
    {
        return 0;
    }

    try
    {
        // open serial port
        asio::error_code ec;
        io_->serial = asio::serial_port(io_->io);
        io_->serial.open(port, ec);
        if (ec)
        {
            std::cerr << "open serial port failed:" << ec.message() << std::endl;
            return -1;
        }
        io_->serial.set_option(asio::serial_port::baud_rate(baud));
        io_->serial.set_option(asio::serial_port::flow_control(
            asio::serial_port::flow_control::none));
        io_->serial.set_option(asio::serial_port::parity(
            asio::serial_port::parity::none));
        io_->serial.set_option(asio::serial_port::stop_bits(
            asio::serial_port::stop_bits::one));
        io_->serial.set_option(asio::serial_port::character_size(8));

        // start io read work thread
        start_read_work_thread();

        return 0;
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << std::endl;
        return -1;
    }
}

void CSerialCommandControllor::Disconnect()
{
    try
    {
        if (io_ != nullptr)
        {
            io_->serial.close();
        }

        io_read_flag_ = false;
        if (io_read_thread_ != nullptr && io_read_thread_->joinable())
        {
            io_read_thread_->join();
        }
        delete io_read_thread_;
        io_read_thread_ = nullptr;
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << std::endl;
    }
}

int32_t CSerialCommandControllor::ResetSwitchPowerCommand(zl::zlmodules::SwitchType switch_type, int32_t seconds)
{
    std::unique_lock<std::mutex> ul(mutex_);
    std::cout << __FUNCTION__ << std::endl;

    if (switch_type == ST_Unknown || seconds <= 0)
    {
        qDebug() << "[ERROR]" << "send reset switch power command error, switch type: " << switch_type << ", seconds: " << seconds;
        return -2;
    }

    // send command
    auto ret = cmd_reset_switch_power_command(switch_type, seconds);
    if (ret != 0)
    {
//        std::cerr << "send reset switch power command return failed: " << ret << std::endl;
        qDebug() << "[ERROR]" << "send reset switch power command return failed: " << ret;
        return -1;
    }

    return 0;
}

int32_t CSerialCommandControllor::cmd_reset_switch_power_command(zl::zlmodules::SwitchType switch_type, int32_t seconds)
{
    if (switch_type == ST_Unknown || seconds <= 0)
    {
        qDebug() << "[ERROR]" << "reset switch power command error, switch type: " << switch_type << ", seconds: " << seconds;
        return -1;
    }

    int32_t value = switch_type + seconds;
    unsigned char cmd_content = value;

    constexpr size_t cmd_len = MIN_LEN;
    unsigned char command = CMD_SWITCH_POWER_RESET;
    unsigned char index = get_cmd_index(command);

    unsigned char cmd[cmd_len]{};

    for (uint i = 0; i < cmd_len; i++)
        cmd[i] = 0x00;

    cmd[0] = CMD_HEAD_FLAG;
    cmd[1] = command;
    cmd[2] = cmd_len;

    cmd[3] = cmd_content;

    cmd[cmd_len-3] = index;
    cmd[cmd_len-2] = genChk(cmd, cmd_len-2);
    cmd[cmd_len-1] = END_FLAG;

    // write command
    auto ret = sync_write_cmd(cmd, cmd_len);
    if (ret < cmd_len)
    {
//        std::cerr << "reset switch power command write to io failed: " << ret << std::endl;
        qDebug() << "[ERROR]" << "reset switch power command write to io failed:" << ret;
        return -1;
    }

    add_cmd_index(command, index);

    return 0;
}

bool CSerialCommandControllor::cv_wait(int32_t timeout)
{
    // wait for reply
    std::unique_lock<std::mutex> ul2(mutex_cv_);
    flag_cv_ = false;
    memset(io_reply_->data, 0, 1024);
    io_reply_->len = 0;

    auto st = cv_.wait_for(ul2, std::chrono::milliseconds(timeout), [&] { return flag_cv_; });
    if (!st)
    {
        std::cerr << "cv wait timeout" << std::endl;
        return false;
    }
    return true;
}

int32_t CSerialCommandControllor::analyze_receive_cmd_buffer()
{
    CommandResultType type(Crt_Unknown);

    type = Crt_Success;

    if (io_reply_->len < MIN_LEN)
    {
        if (io_reply_->len < 1) return -1;

        if (obs_ != nullptr)
            obs_->CommandResult(io_reply_->data[1], Crt_Length_error, io_reply_->data, io_reply_->len);
    }

    unsigned char command = io_reply_->data[1];
    unsigned char len = io_reply_->data[2];
    unsigned char state = io_reply_->data[3];

    unsigned char cmd_index = io_reply_->data[io_reply_->len - 3];
    unsigned char check = io_reply_->data[io_reply_->len - 2];

    if (command < CMD_SWITCH_POWER_RESET) type = Crt_Command_error;
    if (io_reply_->len != len) type = Crt_Length_error;

    if (check != genChk(io_reply_->data, io_reply_->len - 2)) type = Crt_Bcc_error;

    if (state != RET_BUSY_FLAG && state != RET_VALID_FLAG && io_reply_->len == MIN_LEN) type = Crt_State_error;
    if (state == RET_ERROR_FLAG && io_reply_->len == MIN_LEN) type = Crt_Check_fail;

    if (state == RET_BUSY_FLAG && io_reply_->len == MIN_LEN) type = Crt_Machine_busy;

    if (is_index_valid(command, cmd_index) != 0)
    {
        qDebug() << "[ERROR]" << "receive command return error, cmd:" << command << " cmd index:" << cmd_index;

        type = Crt_Index_error;
        return -1;
    }

    if (type == Crt_Machine_busy)
    {
        remove_cmd_index(command, cmd_index);
    }

    if (obs_ != nullptr && type != Crt_Index_error)
    {
        obs_->CommandResult(command, type, io_reply_->data, io_reply_->len);
    }

    return 0;
}

int32_t CSerialCommandControllor::analyze_receive_msg_buffer()
{
    CommandResultType type(Crt_Unknown);

    type = Crt_Success;

    if (io_reply_->len < MIN_LEN)
    {
        if (io_reply_->len < 1) return -1;

        if (obs_ != nullptr)
            obs_->CommandResult(io_reply_->data[1], Crt_Length_error, io_reply_->data, io_reply_->len);
    }

    unsigned char command = io_reply_->data[1];
    unsigned char len = io_reply_->data[2];

    unsigned char cmd_index = io_reply_->data[io_reply_->len - 3];
    unsigned char check = io_reply_->data[io_reply_->len - 2];


    if (command < CMD_SWITCH_POWER_RESET) type = Crt_Command_error;
    if (io_reply_->len != len) type = Crt_Length_error;
    if (check != genChk(io_reply_->data, io_reply_->len - 2)) type = Crt_Bcc_error;

    if (io_reply_->data[3] == RET_ERROR_FLAG && io_reply_->len == MIN_LEN) type = Crt_Check_fail;


    if (is_index_valid(command, cmd_index) != 0)
    {
        qDebug() << "[ERROR]" << "receive msg return error, cmd:" << command << " cmd index:" << cmd_index;

        type = Crt_Index_error;
        return -1;
    }
    else
    {
        remove_cmd_index(command, cmd_index);
    }

    if (obs_ != nullptr)
    {
        if (type == Crt_Success)
            obs_->MsgData(command, io_reply_->data + 3, io_reply_->len - 6);
        else
            obs_->CommandResult(command, type, io_reply_->data, io_reply_->len);
    }

    return 0;
}

size_t CSerialCommandControllor::sync_write_cmd(unsigned char *buff, size_t len, int32_t timeout)
{
    try
    {
        // async handler
        auto phandler = [&](const std::error_code &ec, std::size_t len,
                            std::error_code *ec_out, std::size_t *len_out) -> void {
            *ec_out = ec;
            *len_out = len;
        };

        // async send
        std::size_t length(0);
        std::error_code ec;
        io_->serial.async_write_some(
            asio::buffer(buff, len),
            std::bind(phandler, std::placeholders::_1, std::placeholders::_2, &ec, &length));
        io_->io.restart();
        io_->io.run_for(std::chrono::milliseconds(timeout));
        if (!io_->io.stopped())
        {
            io_->serial.cancel();
            io_->io.run();
        }
        return length;
    }
    catch (const std::exception &e)
    {
        std::cerr << e.what() << '\n';
        return 0;
    }
}
/*
void CSerialCommandControllor::process_package(const unsigned char *pkg, size_t len)
{
    // static cache
    constexpr size_t max_buffer_len = 2 * 65535;
    static unsigned char _buffer[max_buffer_len]{0x00};
    static size_t _buffer_size = 0;

    if (len + _buffer_size >= max_buffer_len)
    {
        memset(_buffer, 0, max_buffer_len);
        _buffer_size = 0;
        return;
    }
    else
    {
        memcpy(_buffer + _buffer_size, pkg, len);
        _buffer_size += len;
    }

    if (_buffer_size <= 2)
    {
        return;
    }

    // parse package
    unsigned char msg_len = _buffer[2];

    while ((_buffer[0] == CMD_HEAD_FLAG || _buffer[0] == MSG_HEAD_FLAG)             // 指令头标记
           &&_buffer[msg_len - 1] == END_FLAG                                       // 指令尾标记
           && msg_len <= _buffer_size                                               //
           )
    {
        std::lock_guard<std::mutex> lg(mutex_cv_);
        memcpy(io_reply_->data, _buffer, msg_len);
        io_reply_->len = msg_len;
        flag_cv_ = true;
        cv_.notify_one();

        if (_buffer[0] == CMD_HEAD_FLAG)
            analyze_receive_cmd_buffer();
        else if (_buffer[0] == MSG_HEAD_FLAG)
            analyze_receive_msg_buffer();


        memcpy(_buffer, _buffer + msg_len, _buffer_size - msg_len);
        _buffer_size -= msg_len;

        if (_buffer_size <= 2)
        {
            return;
        }

        msg_len = _buffer[2];
    }

//    // parse package
//    if ((_buffer[0] == CMD_HEAD_FLAG || _buffer[0] == MSG_HEAD_FLAG) && _buffer[_buffer_size - 1] == END_FLAG)
//    {



//        std::lock_guard<std::mutex> lg(mutex_cv_);
//        memcpy(io_reply_->data, _buffer, _buffer_size);
//        io_reply_->len = _buffer_size;
//        flag_cv_ = true;
//        cv_.notify_one();

//        if (_buffer[0] == CMD_HEAD_FLAG)
//            analyze_receive_cmd_buffer();
//        else if (_buffer[0] == MSG_HEAD_FLAG)
//            analyze_receive_msg_buffer();
//    }

    // clear _buffer
    memset(_buffer, 0, max_buffer_len);
    _buffer_size = 0;
}
*/
void CSerialCommandControllor::process_package(const unsigned char *pkg, size_t len)
{
    // static cache
    constexpr size_t max_buffer_len = 2 * 65535;
    static unsigned char _buffer[max_buffer_len]{0x00};
    static size_t _buffer_size = 0;

    // copy data to cache
    if (pkg == nullptr || len <= 0)
    {
        return;
    }

    if (_buffer_size + len > max_buffer_len)
    {
        _buffer_size = 0;
        return;
    }

    memcpy(_buffer + _buffer_size, pkg, len);
    _buffer_size += len;

    // 数据解析
    size_t current_offset = 0;

    while (_buffer_size > current_offset && _buffer_size > 3)
    {
        // 查找协议头部
        if (_buffer[current_offset + 0] != CMD_HEAD_FLAG && _buffer[current_offset + 0] != MSG_HEAD_FLAG)
        {
            current_offset += 1;
            break;
        }

        size_t required_len = _buffer[current_offset + 2];
        if (required_len > (_buffer_size - current_offset))
        {
            break;
        }

        std::lock_guard<std::mutex> lg(mutex_cv_);

        if (CMD_SWITCH_POWER_RESET == _buffer[current_offset + 1])
        {
            memcpy(io_reply_->data, _buffer+ current_offset, required_len);
            io_reply_->len = required_len;
            flag_cv_ = true;
            cv_.notify_one();
        }

        if (_buffer[current_offset + 0] == CMD_HEAD_FLAG)
            analyze_receive_cmd_buffer();
        else if (_buffer[current_offset + 0] == MSG_HEAD_FLAG)
            analyze_receive_msg_buffer();

        // continue
        current_offset += required_len;
    }


    if (current_offset > 0)
    {
        memcpy(_buffer, _buffer + current_offset, _buffer_size - current_offset);
        _buffer_size -= current_offset;
    }
}

void CSerialCommandControllor::start_read_work_thread()
{
    try
    {
        if (io_read_thread_ != nullptr && io_read_thread_->joinable())
        {
            io_read_thread_->join();
        }
        delete io_read_thread_;
        io_read_flag_ = true;
        io_read_thread_ = new std::thread([&] {
            try
            {
                auto pfunc_handler = [&](const std::error_code &ec, std::size_t len,
                                         std::error_code *ec_out, std::size_t *len_out) {
                    *ec_out = ec;
                    *len_out = len;
                };

                int32_t err_cnt(0);

                // async receive
                while (io_read_flag_)
                {
                    std::size_t length = 0;
                    std::error_code ec;
                    constexpr size_t tmp_len = 512;
                    unsigned char buff[tmp_len]{0x00};
                    io_->serial.async_read_some(asio::buffer(buff, tmp_len),
                                                std::bind(pfunc_handler, std::placeholders::_1, std::placeholders::_2, &ec, &length));
                    io_->io.restart();
                    io_->io.run_for(std::chrono::milliseconds(300));
                    if (!io_->io.stopped())
                    {
                        io_->serial.cancel();
                        io_->io.run();
                    }

                    if (length <= 0)
                    {
                        err_cnt += 1;
                        if (err_cnt >= 2)
                        {
                            process_package(nullptr, 0);                            
                            err_cnt = 0;
                        }
                        continue;
                    }

                    std::cout << "io read: " << std::hex << buff << " " << length << std::endl;
                    process_package(buff, length);
                }
            }
            catch (const std::exception &e)
            {
                std::cerr << e.what() << '\n';
            }
        });
    }
    catch (const std::exception &e)
    {
        std::cerr << "start read work thread failed:" << e.what() << std::endl;
    }
}

unsigned char CSerialCommandControllor::get_cmd_index(unsigned char cmd)
{
    static unsigned char switch_power_reset_index = 0;


    auto funcGetCurrentIndex = [&](unsigned char& index)
    {
        return ((++index) == 0x00 ? (++index) : index);
    };

    switch (cmd)
    {
    case CMD_SWITCH_POWER_RESET: return funcGetCurrentIndex(switch_power_reset_index);
    default: return 0x00;
    }

    return 0x00;
}

int32_t CSerialCommandControllor::add_cmd_index(unsigned char cmd, unsigned char index)
{
    auto funcAddIndexToList = [&](std::list<unsigned char>& cmd_list)
    {
        std::list<unsigned char>::iterator it = cmd_list.begin();

        while (it != cmd_list.end())
        {
            if (index == *it) return -1;

            it++;
        }

        cmd_list.push_back(index);
        return 0;
    };

    switch (cmd)
    {
    case CMD_SWITCH_POWER_RESET: return funcAddIndexToList(switch_power_reset_cmd_list);

    default: return -1;
    }

    return -1;
}

int32_t CSerialCommandControllor::is_index_valid(unsigned char cmd, unsigned char index)
{
    auto funcIndexInList = [&](std::list<unsigned char>& cmd_list)
    {
        std::list<unsigned char>::iterator it = cmd_list.begin();

        while (it != cmd_list.end())
        {
            if (index == *it) return 0;

            it++;
        }

        return -1;
    };

    switch (cmd)
    {
    case CMD_SWITCH_POWER_RESET: return funcIndexInList(switch_power_reset_cmd_list);

    default: return -1;
    }

    return -1;
}

int32_t CSerialCommandControllor::remove_cmd_index(unsigned char cmd, unsigned char index)
{
    auto funcRemoveIndex = [&](std::list<unsigned char>& cmd_list)
    {
        std::list<unsigned char>::iterator it = cmd_list.begin();

        while (it != cmd_list.end())
        {
            if (index == *it)
            {
                it = cmd_list.erase(it);
                return 0;
            }

            it++;
        }

        return -1;
    };

    switch (cmd)
    {
    case CMD_SWITCH_POWER_RESET: return funcRemoveIndex(switch_power_reset_cmd_list);

    default: return -1;
    }

    return -1;
}

} // namespace zlmodules
} // namespace zl
